﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Loki.Utils
{
    internal class ConcurrentCollection<T> : LokiObject, IEnumerable<IListNode<T>>
    {
        private class NodeIterator : IEnumerator<IListNode<T>>
        {
            private ConcurrentListNode<T> _Current;

            public IListNode<T> Current
            {
                get { return _Current; }
            }

            public void Dispose()
            {
                lock (_Parent._SyncLock)
                {
                    if (_Current != null)
                    {
                        _Current.SYN_Read = false;
                    }
                }

                _Parent.StopIterating();
            }

            object IEnumerator.Current
            {
                get { return this.Current; }
            }

            public bool MoveNext()
            {
                lock (_Parent._SyncLock)
                {
                    ConcurrentListNode<T> L_Node = null;
                    if (_Current == null)
                    {
                        L_Node = _Parent._First;
                    }
                    else
                    {
                        L_Node = _Current.Next;
                    }

                    while (L_Node != null && (L_Node.Version > _Version || L_Node.SYN_Deleted))
                    {
                        L_Node = L_Node.Next;
                    }

                    if (L_Node != null)
                    {
                        L_Node.SYN_Read = true;
                    }

                    if (_Current != null)
                    {
                        _Current.SYN_Read = false;
                    }

                    _Current = L_Node;
                }

                return _Current != null;
            }

            public void Reset()
            {
                throw new NotSupportedException();
            }

            private ConcurrentCollection<T> _Parent;

            private long _Version;

            public NodeIterator(ConcurrentCollection<T> P_ParentList, long P_Version)
            {
                _Version = P_Version;
                _Parent = P_ParentList;
            }
        }

        private long _Version;

        private object _SyncLock = new object();

        private ConcurrentListNode<T> _First;

        private ConcurrentListNode<T> _Last;

        private int _IteratorCount;

        private ConcurrentQueue<ConcurrentListNode<T>> _ToBeRemoved;

        public bool IsEmpty
        {
            get { return this._First == null; }
        }

        public ConcurrentCollection()
        {
            _ToBeRemoved = new ConcurrentQueue<ConcurrentListNode<T>>();
        }

        public void Add(T P_Value)
        {
            lock (_SyncLock)
            {
                _Version++;
                ConcurrentListNode<T> L_NodeToAdd = new ConcurrentListNode<T>(P_Value, _Version);

                if (this._Last == null)
                {
                    this._Last = L_NodeToAdd;

                    // visible for read
                    this._First = L_NodeToAdd;
                }
                else
                {
                    ConcurrentListNode<T> L_PreviousLast = this._Last;
                    L_NodeToAdd.Previous = L_PreviousLast;
                    this._Last = L_NodeToAdd;

                    // visible for read
                    L_PreviousLast.Next = L_NodeToAdd;
                }
            }
        }

        private void PurgeItems()
        {
            ConcurrentListNode<T> L_FirstNode = null;
            ConcurrentListNode<T> L_NodeToRemove = null;

            if (_ToBeRemoved.TryPeek(out L_FirstNode))
            {
                while (_ToBeRemoved.Count > 0 && L_FirstNode != L_NodeToRemove)
                {
                    if (_ToBeRemoved.TryDequeue(out L_NodeToRemove))
                    {
                        if (L_NodeToRemove.SYN_Read)
                        {
                            // is read, try later
                            _ToBeRemoved.Enqueue(L_NodeToRemove);
                        }
                        else
                        {
                            InternalRemove(L_NodeToRemove);
                        }
                    }

                    _ToBeRemoved.TryPeek(out L_NodeToRemove);
                }
            }
        }

        private void StopIterating()
        {
            _IteratorCount--;

            if (_IteratorCount == 0)
            {
                PurgeItems();
            }
        }

        public void Remove(IListNode<T> P_NodeToRemove)
        {
            ConcurrentListNode<T> L_Node = P_NodeToRemove as ConcurrentListNode<T>;
            L_Node.SYN_Deleted = true;
            if (_IteratorCount == 0)
            {
                PurgeItems();
            }
            else
            {
                _ToBeRemoved.Enqueue(L_Node);
            }
        }

        private void InternalRemove(ConcurrentListNode<T> P_NodeToRemove)
        {
            lock (_SyncLock)
            {
                if (P_NodeToRemove.SYN_Read)
                {
                    // is read ; try later
                    _ToBeRemoved.Enqueue(P_NodeToRemove);
                    return;
                }

                var L_PreviousNode = P_NodeToRemove.Previous;
                var L_NextNode = P_NodeToRemove.Next;

                if (L_PreviousNode != null)
                {
                    // remove from read if in the list
                    L_PreviousNode.Next = L_NextNode;
                }

                if (P_NodeToRemove == this._First)
                {
                    // remove from read if first
                    this._First = L_NextNode;
                }

                if (L_NextNode != null)
                {
                    L_NextNode.Previous = L_PreviousNode;
                }

                if (P_NodeToRemove == this._Last)
                {
                    this._Last = L_PreviousNode;
                }

                P_NodeToRemove.Next = null;
                P_NodeToRemove.Previous = null;
            }
        }

        public IEnumerator<IListNode<T>> GetEnumerator()
        {
            _IteratorCount++;
            return new ConcurrentCollection<T>.NodeIterator(this, this._Version);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
}
